package com.gmrz.uaf.servlet.v1.handler;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.UAFAuthInitProcessor;
import com.gmrz.uaf.protocol.v1.schema.AuthenticationRequest;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPAuthInitProcessor;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPCertAuthInitProcessor;
import com.gmrz.uaf.server.ServerContext;
import com.gmrz.uaf.servlet.v1.request.AuthContext;
import com.gmrz.uaf.servlet.v1.request.ReceiveReqPayload;
import com.gmrz.uaf.servlet.v1.response.FidoRespPayload;
import com.gmrz.uaf.servlet.v1.validation.PayloadValidationException;
import com.gmrz.uaf.servlet.v1.validation.RestValidator;
import com.gmrz.util.Convert;
import com.gmrz.util.Strings;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.util.List;

public class UAFAuthRequestHandler extends UAFBaseHandler {
    private static final Logger LOG = LogManager.getLogger(UAFAuthRequestHandler.class);

    /**
     * {
     * "context":{
     * "transNo":"32characters",
     * "userName":"user1",
     * "appID","001",
     * "deviceID","abcdefg",
     * "transType":"00"
     * "authType":["00","01"],
     * "transactionText":"base64 encoded transaction text"
     * }
     * }
     *
     * @param request
     * @return {
     * "statusCode":1200,
     * "description":"OK",
     * "uafRequest":"[
     * "[{\"header\":{....},\"policy\":{.....}}]"
     * ]"
     * }
     */
    public String authInit(String request) {

        LOG.info("[/auth/receive][begin]");
        ServerContext.getServerContext().getFidoLog().setRequest(request);
        Gson gson = UAFSchemaBuilder.getGson();
        FidoRespPayload<String> respPayload = new FidoRespPayload<String>();
        Type reqType = new TypeToken<ReceiveReqPayload<AuthContext>>() {
        }.getType();
        String response = null;
        try {
            // 将请求json串转为对象
            ReceiveReqPayload<AuthContext> reqPayload = gson.fromJson(request, reqType);
            // 兼容 旧接口的deviceID
            if(Strings.isNullOrEmpty(reqPayload.getContext().getDeviceID())){
                reqPayload.getContext().setDeviceID(reqPayload.getContext().getDevices().getDeviceID());
            }
            //记录日志
            logRequest(request, reqPayload.getContext().getTransNo(), reqPayload.getContext().getUserName(),
                    reqPayload.getContext().getAppID(), reqPayload.getContext().getAuthType().get(0),reqPayload.getContext().getTransType(), Constants.Method.AUTH_INIT);
            // 校验认证接口的状态
            checkUafAuthStatus();

            ServerContext.getServerContext().setPerform(reqPayload.getContext().getAppID());
            ServerContext.getServerContext().setBase64(reqPayload.getContext().isBase64());
            if(this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_4 && !ServerContext.getServerContext().isPerform()) {
                //验证厂商白名单和设备黑名单
                validateDevice(reqPayload.getContext().getDevices(), reqPayload.getContext().getAppID());
            }
            if(this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_1 && !ServerContext.getServerContext().isPerform()) {
                //验证context参数
                validate(reqPayload);
            }
            UAFAuthInitProcessor processor = null;
            if (reqPayload.getContext().getAuthType().get(0).compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0) {
                processor = GuiceUtil.getProcessorInjector().getInstance(UAPCertAuthInitProcessor.class);
            }else if (reqPayload.getContext().getAuthType().get(0).compareTo(Constants.AuthType.REMOTE_FACEPRINT.getAuthType()) >= 0) {
                processor = GuiceUtil.getProcessorInjector().getInstance(UAPAuthInitProcessor.class);
            } else {
                processor = GuiceUtil.getProcessorInjector().getInstance(UAFAuthInitProcessor.class);
            }

            AuthContext context = reqPayload.getContext();
            // hash 用户名
            String hashedUserName = generateHashedUserName(context.getUserName(), context.getRf1(), context.getRf2());
            // 原用户名
            String custNo = context.getUserName();
            String challenge = context.getChallenge();
            byte[] challengeByte = null;
            if(Strings.isNotBlank(challenge)){
                challengeByte  = challenge.getBytes("utf-8");
            }else {
                challengeByte = new byte[0];
            }
            List<AuthenticationRequest> requests = processor.request(custNo,
                    hashedUserName, context.getAppID(),
                    context.getDeviceID(), context.getAuthType(), context.getTransType(),
                    context.getTransactionText(),challengeByte,context.getExt());
            LOG.info("requests=" + gson.toJson(requests));
            if(this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_1 && !ServerContext.getServerContext().isPerform()) {
                // 缓存发起接口的 context 参数
                saveContextToRedis(context, requests, hashedUserName);
            }
            response = buildReceiveResponse(gson, respPayload, requests);
        } catch (Exception ex) {
            LOG.error("authInit error",ex);
            response = buildFidoRespFromException(respPayload, ex);
        }
        LOG.info("[/auth/receive][end]");
        return response;
    }

    /**
     * 验证context参数
     * @param reqPayload
     * @throws PayloadValidationException
     */
    private void validate(ReceiveReqPayload<AuthContext> reqPayload) throws PayloadValidationException, UnsupportedEncodingException {
        if ((null == reqPayload) || (null == reqPayload.getContext())) {
            throw new PayloadValidationException("context", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        AuthContext context = reqPayload.getContext();
        RestValidator.validate("userName", context.getUserName());
        RestValidator.validate("appID", context.getAppID());
        List<String> authTypeList = context.getAuthType();
        if(null == authTypeList || authTypeList.size() <= 0){
            throw new PayloadValidationException("authType",RestValidator.VALIDATION_ERROR.EMPTY);
        }
        if(authTypeList.size() > 1){
            throw new PayloadValidationException("authType",RestValidator.VALIDATION_ERROR.SUPPORT_ONE_TYPE);
        }
        for (String auth : authTypeList) {
            RestValidator.validate("authType", auth);
        }
        RestValidator.validate("rf1", context.getRf1());
        RestValidator.validate("rf2", context.getRf2());
        RestValidator.validate("transType", context.getTransType());
        RestValidator.validate("deviceID", context.getDeviceID());
        RestValidator.validate("transNo", context.getTransNo());
        RestValidator.validate("ext", context.getExt());

        String transactionText = context.getTransactionText();
        RestValidator.validate("transactionText", transactionText);
        if (!Strings.isNullOrEmpty(transactionText)) {
            byte[] txdata = Convert.fromBase64(transactionText);
            try {
                new String(txdata, "UTF-8");
            } catch (Exception e) {
                LOG.error(e);
                throw new PayloadValidationException("transactionText",
                        RestValidator.VALIDATION_ERROR.INVALID_CHARACTERS);
            }
        }
        RestValidator.validateDevices(context.getDevices());

        String challenge = context.getChallenge();
        if(Strings.isNotBlank(challenge)){
            if(challenge.getBytes("utf-8").length > 32){
                throw new PayloadValidationException("challenge", RestValidator.VALIDATION_ERROR.INVALID_LENGTH);
            }
        }
    }

    /**
     * 缓存发起接口的 context 参数
     * @param context
     * @param requests
     * @param hashedUserName
     * @throws ChallengeStoreException
     */
    private void saveContextToRedis(AuthContext context, List<AuthenticationRequest> requests, String hashedUserName) throws ChallengeStoreException {
        String challenge = Convert.toBase64(requests.get(0).getOperationHeader().getServerData().getChallenge());
        String param = getValidateData(context.getUserName(), context.getRf1(), context.getRf2(),
                context.getAppID(), null, context.getTransType(), context.getAuthType(),context.getChallenge());
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        challengeStoreService.setRedisValue(Constants.UAP_AUTH_CONTEXT_CACHE_KEY + challenge + "-" + hashedUserName, 300, param);

    }
}
